/**
 * 2009 20 Jul 2009
/*

/**
 * 
 */
package org.idimitrov.pdftools.pdfchecker;

import java.io.IOException;

import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PRStream;
import com.lowagie.text.pdf.PRTokeniser;
import com.lowagie.text.pdf.PdfArray;
import com.lowagie.text.pdf.PdfName;
import com.lowagie.text.pdf.PdfNumber;
import com.lowagie.text.pdf.PdfObject;
import com.lowagie.text.pdf.PdfReader;

/**
 * Contains methods for checking pdf files for colour/grayscale/black and white
 * content.
 * 
 * @author Ivaylo Dimitrov
 */
public class PDFChecker {
    public void checkOrientation(String path) {
	try {
	    PdfReader reader = new PdfReader(path);
	    for (int i = 1; i <= reader.getNumberOfPages(); i++) {
		Rectangle r = reader.getPageSize(i);
		if (r.getWidth() > r.getHeight()) {
		    System.out.println("Page " + i + ": Landscape");
		} else {
		    System.out.println("Page " + i + ": Portrait");
		}
		System.out.println("\t" + reader.getPageSize(i));
	    }
	} catch (IOException e) {
	    e.printStackTrace();
	}
    }

    public void checkColourOld(String path) throws IOException {
	System.out.println("Checking " + path);
	ImageManager im = new ImageManager();
	if (im.consistColorImages(path)) {
	    System.out.println("COLOUR");
	}
	if (im.consistGrayScaleImages(path)) {
	    System.out.println("GRAYSCALE");
	}
	if (im.consistBlackAndWhiteImages(path)) {
	    System.out.println("B&W");
	}
	// if (im.consistColorText(path)) {
	// System.out.println("COLOUR TEXT");
	// }
    }

    public String checkColour(String path) throws IOException {
	PdfReader reader = null;
	try {
	    reader = new PdfReader(path);
	    reader.consolidateNamedDestinations();
	    reader.createFakeFontSubsets();

	    for (int n = 1; n < reader.getXrefSize(); ++n) {
		PdfObject obj = reader.getPdfObjectRelease(n);

		if (obj == null) {
		    continue;
		}

		if (obj.isStream()) {
		    PRStream stream = (PRStream) obj;
		    if (stream.contains(PdfName.TYPE)
			    && ((stream.get(PdfName.TYPE))
				    .equals(PdfName.XOBJECT))) {
			if (stream.contains(PdfName.SUBTYPE)
				&& (((PdfName) stream.get(PdfName.SUBTYPE))
					.equals(PdfName.IMAGE))) {
			    checkImageInXObject(obj, stream);
			}
		    } else {
			checkImageInStream(stream);
			checkTextInStream(stream);
		    }
		}
	    }
	} catch (IOException e) {
	    throw new IOException(
		    "WARNING: failed to check the colour in pdf file " + path
			    + " with message " + e.getMessage(), e);
	}
	return "";
    }

    private void checkImageInXObject(PdfObject obj, PRStream stream) {
	obj = stream.get(PdfName.COLORSPACE);
	String colorSpace = "";
	while (obj != null && (!(obj.isName()))) {
	    if (obj.isIndirect()) {
		obj = PdfReader.getPdfObjectRelease(obj);
	    } else {
		if (obj.isArray()) {
		    PdfArray arr = (PdfArray) obj;
		    obj = (PdfObject) arr.getArrayList().get(0);
		    if (obj.isName() && (obj.equals(PdfName.INDEXED))) {
			colorSpace = "Indexed/";
			obj = (PdfObject) arr.getArrayList().get(1);
		    }
		}
	    }
	}

	if (obj != null) {
	    colorSpace = colorSpace + ((PdfName) obj).toString().substring(1);
	}

	int bitsPerComp = ((PdfNumber) stream.get(PdfName.BITSPERCOMPONENT))
		.intValue();

	// set flags
	boolean isGray = bitsPerComp > 1
		&& (colorSpace.equals("DeviceGray")
			|| colorSpace.equals("CalGray")
			|| colorSpace.equals("Indexed/CalGray") || colorSpace
			.equals("Indexed/GrayScale"));
	boolean isColor = bitsPerComp > 1
		&& (!(colorSpace.equals("DeviceGray") || colorSpace
			.equals("CalGray")));
	boolean isBlackAndWhite = bitsPerComp == 1;
    }

    private void checkImageInStream(PRStream stream) throws IOException {
	int iStartPos = 0;
	String sStreamContent = new String(PdfReader.getStreamBytes(stream));
	// System.out.println(sStreamContent);
	while ((iStartPos = getTokenPos("BI", sStreamContent, iStartPos)) != -1) {
	    int iEndPos = getTokenPos("ID", sStreamContent, iStartPos);
	    String sTextBlock = sStreamContent
		    .substring(iStartPos, iEndPos + 2);
	    iStartPos = iStartPos + 2;

	    // System.out.println(sTextBlock);
	    PRTokeniser tk = new PRTokeniser(sTextBlock.getBytes());

	    String operator = "";
	    String colorSpace = "";
	    String name = "";
	    int bitsPerComp = 0;
	    boolean bInArray = false;

	    while (tk.nextToken()) {
		if (tk.getTokenType() == PRTokeniser.TK_OTHER) {
		    operator = tk.getStringValue();
		} else if (tk.getTokenType() == PRTokeniser.TK_START_ARRAY) {
		    bInArray = true;
		    name = operator;
		} else if (tk.getTokenType() == PRTokeniser.TK_END_ARRAY) {
		    bInArray = false;
		} else if (tk.getTokenType() == PRTokeniser.TK_NUMBER) {
		    if (operator.equals("BPC")) {
			System.out.println(bitsPerComp);
			bitsPerComp = tk.intValue();
		    }
		} else if (tk.getTokenType() == PRTokeniser.TK_NAME) {
		    operator = tk.getStringValue();

		    if (name.equals("CS") && !bInArray) {
			colorSpace = colorSpace + tk.getStringValue();
			break;
		    }

		    if (operator.equals("CS")) {
			name = operator;
		    } else if (operator.equals("I") && name.equals("CS")
			    && bInArray) {
			colorSpace = "Indexed/";
			tk.nextToken();
			if (tk.getTokenType() == PRTokeniser.TK_NAME) {
			    colorSpace = colorSpace + tk.getStringValue();
			    break;
			}
		    }

		}
	    }

	    // set flags
	    boolean isGray = bitsPerComp > 1
		    && (colorSpace.equals("DeviceGray")
			    || colorSpace.equals("CalGray")
			    || colorSpace.equals("Indexed/CalGray") || colorSpace
			    .equals("Indexed/GrayScale"));
	    boolean isColor = bitsPerComp > 1
		    && (!(colorSpace.equals("DeviceGray") || colorSpace
			    .equals("CalGray")));
	    boolean isBlackAndWhite = bitsPerComp == 1;
	}
    }

    private void checkTextInStream(PRStream stream) throws IOException {
	int iStartPos = 0;
	String sStreamContent = new String(PdfReader.getStreamBytes(stream));
	while ((iStartPos = getTokenPos("BT", sStreamContent, iStartPos)) != -1) {
	    int iEndPos = getTokenPos("ET", sStreamContent, iStartPos);
	    String sTextBlock = sStreamContent
		    .substring(iStartPos, iEndPos + 2);
	    iStartPos = iStartPos + 2;
	    PRTokeniser tk = new PRTokeniser(sTextBlock.getBytes());

	    String operator = "";
	    String colorSpace = "";
	    String name = "";
	    int bitsPerComp = 0;
	    boolean bInArray = false;

	    while (tk.nextToken()) {
		// Check if the operator is: rg/RG, k/K or sc/SC/scn/SCN (with
		// more than 1 parameter)
		// then the document contains colour
		// otherwise it is grayscaled or B/W
		if (tk.getTokenType() == PRTokeniser.TK_OTHER) {
		    operator = tk.getStringValue();
		} else if (tk.getTokenType() == PRTokeniser.TK_START_ARRAY) {
		    bInArray = true;
		    name = operator;
		} else if (tk.getTokenType() == PRTokeniser.TK_END_ARRAY) {
		    bInArray = false;
		} else if (tk.getTokenType() == PRTokeniser.TK_NUMBER) {
		    if (operator.equals("BPC")) {
			bitsPerComp = tk.intValue();
		    } else if (operator.equals("BPC")) {

		    }
		} else if (tk.getTokenType() == PRTokeniser.TK_NAME) {
		    operator = tk.getStringValue();

		    if (name.equals("CS") && !bInArray) {
			colorSpace = colorSpace + tk.getStringValue();
			break;
		    }

		    if (operator.equals("CS")) {
			name = operator;
		    } else if (operator.equals("I") && name.equals("CS")
			    && bInArray) {
			colorSpace = "Indexed/";
			tk.nextToken();
			if (tk.getTokenType() == PRTokeniser.TK_NAME) {
			    colorSpace = colorSpace + tk.getStringValue();
			    break;
			}
		    }

		}
	    }

	    // set flags
	    boolean isGray = bitsPerComp > 1
		    && (colorSpace.equals("DeviceGray")
			    || colorSpace.equals("CalGray")
			    || colorSpace.equals("Indexed/CalGray") || colorSpace
			    .equals("Indexed/GrayScale"));
	    boolean isColor = bitsPerComp > 1
		    && (!(colorSpace.equals("DeviceGray") || colorSpace
			    .equals("CalGray")));
	    boolean isBlackAndWhite = bitsPerComp == 1;
	}
    }

    /**
     * Returns the position of the token in the text block
     */
    public static int getTokenPos(String sName, String s, int iPos) {
	int i = iPos;
	int iLen = sName.length();

	int i1 = 0, i2 = 0;
	String sCheck = "";
	PRTokeniser tkCheck = null;
	while ((i = s.indexOf(sName, i)) != -1) {
	    i1 = i;
	    i2 = i + iLen;

	    if (i1 > 0) {
		i1 -= 1;
	    }
	    if (i2 < (s.length() - 1)) {
		i2 += 1;
	    }
	    sCheck = s.substring(i1, i2);

	    try {
		tkCheck = new PRTokeniser(sCheck.getBytes());
		if (tkCheck.nextToken()) {
		    if ((tkCheck.getTokenType() == PRTokeniser.TK_OTHER)
			    && (tkCheck.getStringValue().compareTo(sName) == 0)) {
			break;
		    }
		    i += iLen;
		}
	    } catch (IOException e) {
		i += iLen;
	    }
	}
	return i;
    }

    /**
     * @param args
     */
    public static void main(String[] args) {

	try {
	    PDFChecker checker = new PDFChecker();
	    // checker.checkColour("org/idimitrov/pdftools/pdfchecker/bad.pdf");
	    // checker.checkOrientation("org/idimitrov/pdftools/pdfchecker/test1.pdf");
	    // checker.checkColour("org/idimitrov/pdftools/pdfchecker/testBWimg.pdf");
	    // checker
	    // .checkColourOld("org/idimitrov/pdftools/pdfchecker/testCOLimg.pdf");
	    // checker.checkColour("org/idimitrov/pdftools/pdfchecker/testBWtxt.pdf");
	    // checker.checkColour("org/idimitrov/pdftools/pdfchecker/testCOLtxt.pdf");
	    // checker.checkColour("org/idimitrov/pdftools/pdfchecker/test1.pdf");
	    // checker.checkColour("org/idimitrov/pdftools/pdfchecker/test2.pdf");
	    // checker.checkColour("org/idimitrov/pdftools/pdfchecker/test3.pdf");
	    // checker.checkOrientation("org/idimitrov/pdftools/pdfchecker/bad.pdf");
	    // checker
	    // .checkColourOld("org/idimitrov/pdftools/pdfchecker/pdfwithcolortext.pdf");
	    // checker
	    // .checkColourOld("org/idimitrov/pdftools/pdfchecker/pdfwithlinkandsquares.pdf");
	    checker
		    .checkColourOld("org/idimitrov/pdftools/pdfchecker/pdfwithcolourflowcharts.pdf");

	    // TODO Detect text-based PDFs: testBWtxt, testCOLtxt, test1 and
	    // test2
	    // are text-based rather than image-based, that's why they are not
	    // detected;

	    // TODO add m_checkColorText attribute and do not confuse checking
	    // colour image with checking colour text

	    // TODO check the text CS to determine the colour ?
	} catch (IOException e) {
	    e.printStackTrace();
	}
	/*
	 * Color Operators: CS, cs, SC, SCN, sc, scn, G, g, RG, rg, K, k
	 * 
	 * Set colour space to colour (not gray): sc/SC (with more than 1
	 * parameter), scn/SCN (the same as sc/SC), rg/RG, k/K Set colour space
	 * to gray: sc/SC (with 1 parameter), scn/SCN (same as sc/SC), g/G
	 */

    }
}
